export abstract class Http<Result = any> {
    abstract request(url: string, options: HttpRequestOptions): Promise<any>

    get<Data extends Result>(url: string, params?: Record<string, any>, options?: Omit<HttpRequestOptions,'method' | 'query' | 'responseType'>): Promise<Data> {
        return this.request(url, {
            method: 'GET',
            query: params,
            ...options
        })
    }

    postForm<Data extends Result>(url: string, params?: Record<string, any>, options?:  Omit<HttpRequestOptions,'method' | 'body' | 'responseType'>): Promise<Data> {
        return this.request(url, {
            method: 'POST',
            body: new URLSearchParams(params),
            ...options
        })
    }

    post<Data extends Result>(url: string, params?: Record<string, any>, options?:  Omit<HttpRequestOptions,'method' | 'body' | 'responseType'>): Promise<Data> {
        return this.request(url, {
            method: 'POST',
            body: params,
            header: {
                'Content-Type': 'application/json'
            },
            ...options
        })
    }

    download<Type extends keyof ResponseTypeMap>(url: string, options?: Omit<HttpRequestOptions,'responseType'> & {responseType: Type}): Promise<ResponseTypeMap[Type]> {
        return this.request(url, {
            method: 'GET',
            ...options
        })
    }

    upload(url: string, file: Blob, options?: Omit<HttpRequestOptions, 'responseType'> & { name?: string, fileName?: string }) {
        const form = new FormData()
        form.append(options?.name ?? 'file', file, options?.fileName)
        return this.request(url, {
            method: 'POST',
            body: form,
            ...options
        })
    }
}

export type HttpRequestOptions = {
    method?: 'GET' | 'POST'
    query?: Record<string, any>
    body?: Record<string, any> | FormData
    header?: Record<string, string>
    signal?: AbortSignal,
    responseType?: keyof ResponseTypeMap
}

export type ResponseTypeMap = {
    json: any
    text: string
    arraybuffer: ArrayBuffer
    blob: Blob
}

export type HttpRequestConfig = {
    url: string
    options: HttpRequestOptions
}

export class HttpError extends Error {
    isAbort: boolean = false

    constructor(
        message: string,
        public config: HttpRequestConfig,
        public response?: {
            status: number
            body?: any
        }
    ) {
        super(message)
    }
}
